import tkinter as tk
from tkinter import ttk, messagebox
import pandas as pd
import os


class FileMergerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("文件合并工具")
        self.root.geometry("1200x800")

        # 创建主框架为PanedWindow
        self.main_frame = ttk.PanedWindow(self.root, orient="horizontal")
        self.main_frame.pack(fill="both", expand=True)

        # 创建左侧和右侧框架
        self.left_panel = ttk.Frame(self.main_frame)
        self.right_panel = ttk.Frame(self.main_frame, width=300)

        # 添加到PanedWindow
        self.main_frame.add(self.left_panel, weight=1)
        self.main_frame.add(self.right_panel, weight=0)

        # 防止右侧面板被压缩
        self.right_panel.pack_propagate(False)

        # 存储文件路径和数据
        self.file1_path = None
        self.file2_path = None
        self.df1 = None
        self.df2 = None

        # 存储字段选择状态
        self.field_vars1 = {}
        self.field_vars2 = {}

        self.create_widgets()

    def create_widgets(self):
        # 在左侧面板创建主要内容
        # 文件1选择
        frame1 = ttk.LabelFrame(self.left_panel, text="文件1", padding=10)
        frame1.pack(fill="x", pady=5)

        ttk.Label(frame1, text="文件路径:").pack(side="left")
        self.file1_entry = ttk.Entry(frame1, width=50)
        self.file1_entry.pack(side="left", padx=5)
        ttk.Button(frame1, text="加载文件", command=lambda: self.load_file(1)).pack(side="left")

        # 文件1预览（添加水平和垂直滚动条）
        preview_frame1 = ttk.LabelFrame(self.left_panel, text="文件1预览", height=150)
        preview_frame1.pack(fill="x", pady=5)
        preview_frame1.pack_propagate(False)  # 固定高度

        # 创建预览容器
        preview_container1 = ttk.Frame(preview_frame1)
        preview_container1.pack(fill="both", expand=True, padx=5, pady=5)

        # 创建滚动条
        y_scroll1 = ttk.Scrollbar(preview_container1)
        x_scroll1 = ttk.Scrollbar(preview_container1, orient="horizontal")
        self.preview1 = ttk.Treeview(preview_container1,
                                     yscrollcommand=y_scroll1.set,
                                     xscrollcommand=x_scroll1.set)

        # 布局
        y_scroll1.pack(side="right", fill="y")
        x_scroll1.pack(side="bottom", fill="x")
        self.preview1.pack(side="left", fill="both", expand=True)

        # 配置滚动条
        y_scroll1.config(command=self.preview1.yview)
        x_scroll1.config(command=self.preview1.xview)

        # 文件2选择
        frame2 = ttk.LabelFrame(self.left_panel, text="文件2", padding=10)
        frame2.pack(fill="x", pady=5)

        ttk.Label(frame2, text="文件路径:").pack(side="left")
        self.file2_entry = ttk.Entry(frame2, width=50)
        self.file2_entry.pack(side="left", padx=5)
        ttk.Button(frame2, text="加载文件", command=lambda: self.load_file(2)).pack(side="left")

        # 文件2预览（添加水平和垂直滚动条）
        preview_frame2 = ttk.LabelFrame(self.left_panel, text="文件2预览", height=150)
        preview_frame2.pack(fill="x", pady=5)
        preview_frame2.pack_propagate(False)  # 固定高度

        # 创建预览容器
        preview_container2 = ttk.Frame(preview_frame2)
        preview_container2.pack(fill="both", expand=True, padx=5, pady=5)

        # 创建滚动条
        y_scroll2 = ttk.Scrollbar(preview_container2)
        x_scroll2 = ttk.Scrollbar(preview_container2, orient="horizontal")
        self.preview2 = ttk.Treeview(preview_container2,
                                     yscrollcommand=y_scroll2.set,
                                     xscrollcommand=x_scroll2.set)

        # 布局
        y_scroll2.pack(side="right", fill="y")
        x_scroll2.pack(side="bottom", fill="x")
        self.preview2.pack(side="left", fill="both", expand=True)

        # 配置滚动条
        y_scroll2.config(command=self.preview2.yview)
        x_scroll2.config(command=self.preview2.xview)

        # 合并选项
        options_frame = ttk.LabelFrame(self.left_panel, text="合并选项", padding=10)
        options_frame.pack(fill="x", pady=5)

        # Key选择
        ttk.Label(options_frame, text="选择合并key:").pack(side="left")
        self.key_var1 = tk.StringVar()
        self.key_combo1 = ttk.Combobox(options_frame, textvariable=self.key_var1)
        self.key_combo1.pack(side="left", padx=5)

        ttk.Label(options_frame, text="对应:").pack(side="left")
        self.key_var2 = tk.StringVar()
        self.key_combo2 = ttk.Combobox(options_frame, textvariable=self.key_var2)
        self.key_combo2.pack(side="left", padx=5)

        # 在右侧面板创建字段选择区域
        # 文件1字段选择
        fields_frame1 = ttk.LabelFrame(self.right_panel, text="文件1字段", padding=10)
        fields_frame1.pack(fill="x", pady=5)

        # 创建字段选择容器
        fields_container1 = ttk.Frame(fields_frame1, height=200)
        fields_container1.pack(fill="x")
        fields_container1.pack_propagate(False)  # 固定高度

        # 创建带滚动条的画布
        canvas1 = tk.Canvas(fields_container1)
        scrollbar1 = ttk.Scrollbar(fields_container1, orient="vertical", command=canvas1.yview)

        # 创建框架来容纳复选框
        self.fields_frame1 = ttk.Frame(canvas1)

        # 配置画布
        canvas1.configure(yscrollcommand=scrollbar1.set)
        canvas1.pack(side="left", fill="both", expand=True)
        scrollbar1.pack(side="right", fill="y")

        # 在画布上创建窗口
        canvas1.create_window((0, 0), window=self.fields_frame1, anchor="nw", width=canvas1.winfo_reqwidth())

        # 控制按钮
        control_frame1 = ttk.Frame(fields_frame1)
        control_frame1.pack(fill="x", pady=5)
        ttk.Button(control_frame1, text="全选", command=lambda: self.select_all_fields(1)).pack(side="left", padx=2)
        ttk.Button(control_frame1, text="取消全选", command=lambda: self.deselect_all_fields(1)).pack(side="left",
                                                                                                      padx=2)

        # 文件2字段选择
        fields_frame2 = ttk.LabelFrame(self.right_panel, text="文件2字段", padding=10)
        fields_frame2.pack(fill="x", pady=5)

        # 创建字段选择容器
        fields_container2 = ttk.Frame(fields_frame2, height=200)
        fields_container2.pack(fill="x")
        fields_container2.pack_propagate(False)  # 固定高度

        # 创建带滚动条的画布
        canvas2 = tk.Canvas(fields_container2)
        scrollbar2 = ttk.Scrollbar(fields_container2, orient="vertical", command=canvas2.yview)

        # 创建框架来容纳复选框
        self.fields_frame2 = ttk.Frame(canvas2)

        # 配置画布
        canvas2.configure(yscrollcommand=scrollbar2.set)
        canvas2.pack(side="left", fill="both", expand=True)
        scrollbar2.pack(side="right", fill="y")

        # 在画布上创建窗口
        canvas2.create_window((0, 0), window=self.fields_frame2, anchor="nw", width=canvas2.winfo_reqwidth())

        # 控制按钮
        control_frame2 = ttk.Frame(fields_frame2)
        control_frame2.pack(fill="x", pady=5)
        ttk.Button(control_frame2, text="全选", command=lambda: self.select_all_fields(2)).pack(side="left", padx=2)
        ttk.Button(control_frame2, text="取消全选", command=lambda: self.deselect_all_fields(2)).pack(side="left",
                                                                                                      padx=2)

        # 操作按钮区域
        button_frame = ttk.LabelFrame(self.right_panel, text="操作", padding=10)
        button_frame.pack(fill="x", pady=5)

        ttk.Button(button_frame, text="生成预览", command=self.preview_merge).pack(fill="x", pady=2)
        ttk.Button(button_frame, text="合并并保存", command=self.merge_and_save).pack(fill="x", pady=2)

        # 保存路径
        save_frame = ttk.LabelFrame(self.right_panel, text="保存设置", padding=10)
        save_frame.pack(fill="x", pady=5)

        ttk.Label(save_frame, text="保存路径:").pack(fill="x")
        self.save_entry = ttk.Entry(save_frame)
        self.save_entry.pack(fill="x", pady=2)

        # 预览结果（添加水平和垂直滚动条）
        preview_frame = ttk.LabelFrame(self.left_panel, text="合并预览", padding=10)
        preview_frame.pack(fill="both", expand=True, pady=5)

        # 创建预览容器
        preview_container = ttk.Frame(preview_frame)
        preview_container.pack(fill="both", expand=True)

        # 创建滚动条
        y_scroll = ttk.Scrollbar(preview_container)
        x_scroll = ttk.Scrollbar(preview_container, orient="horizontal")
        self.result_preview = ttk.Treeview(preview_container,
                                           yscrollcommand=y_scroll.set,
                                           xscrollcommand=x_scroll.set)

        # 布局
        y_scroll.pack(side="right", fill="y")
        x_scroll.pack(side="bottom", fill="x")
        self.result_preview.pack(side="left", fill="both", expand=True)

        # 配置滚动条
        y_scroll.config(command=self.result_preview.yview)
        x_scroll.config(command=self.result_preview.xview)

        # 绑定画布的配置事件
        self.fields_frame1.bind("<Configure>", lambda e: self._on_frame_configure(canvas1))
        self.fields_frame2.bind("<Configure>", lambda e: self._on_frame_configure(canvas2))

        # 绑定鼠标滚轮事件
        canvas1.bind_all("<MouseWheel>", lambda e: self._on_mousewheel(canvas1, e))
        canvas2.bind_all("<MouseWheel>", lambda e: self._on_mousewheel(canvas2, e))

    def _on_frame_configure(self, canvas):
        canvas.configure(scrollregion=canvas.bbox("all"))

    def _on_mousewheel(self, canvas, event):
        canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")

    def select_all_fields(self, file_num):
        if file_num == 1:
            for var in self.field_vars1.values():
                var.set(1)
        else:
            for var in self.field_vars2.values():
                var.set(1)

    def deselect_all_fields(self, file_num):
        if file_num == 1:
            for var in self.field_vars1.values():
                var.set(0)
        else:
            for var in self.field_vars2.values():
                var.set(0)

    def load_file(self, file_num):
        if file_num == 1:
            file_path = self.file1_entry.get().strip()
        else:
            file_path = self.file2_entry.get().strip()

        if not file_path:
            messagebox.showerror("错误", "请输入文件路径")
            return

        if not os.path.exists(file_path):
            messagebox.showerror("错误", "文件不存在")
            return

        try:
            if file_path.endswith('.csv'):
                df = pd.read_csv(file_path)
            else:
                df = pd.read_excel(file_path)

            if file_num == 1:
                self.file1_path = file_path
                self.df1 = df
                self.update_preview(self.preview1, df)
                self.update_fields(1, df.columns)
                self.key_combo1['values'] = list(df.columns)
            else:
                self.file2_path = file_path
                self.df2 = df
                self.update_preview(self.preview2, df)
                self.update_fields(2, df.columns)
                self.key_combo2['values'] = list(df.columns)

        except Exception as e:
            messagebox.showerror("错误", f"读取文件时出错：{str(e)}")

    def update_preview(self, tree, df):
        # 清除现有项
        for item in tree.get_children():
            tree.delete(item)

        # 设置列
        tree['columns'] = list(df.columns)
        tree['show'] = 'headings'

        for col in df.columns:
            tree.heading(col, text=col)
            tree.column(col, width=100)

        # 添加前5行数据
        for idx, row in df.head().iterrows():
            tree.insert('', 'end', values=list(row))

    def update_fields(self, file_num, columns):
        if file_num == 1:
            # 清除现有的复选框
            for widget in self.fields_frame1.winfo_children():
                widget.destroy()
            self.field_vars1.clear()

            # 创建新的复选框
            for col in columns:
                var = tk.IntVar()
                self.field_vars1[col] = var
                ttk.Checkbutton(self.fields_frame1, text=col, variable=var).pack(anchor="w", padx=5, pady=2)
        else:
            # 清除现有的复选框
            for widget in self.fields_frame2.winfo_children():
                widget.destroy()
            self.field_vars2.clear()

            # 创建新的复选框
            for col in columns:
                var = tk.IntVar()
                self.field_vars2[col] = var
                ttk.Checkbutton(self.fields_frame2, text=col, variable=var).pack(anchor="w", padx=5, pady=2)

    def get_selected_fields(self, file_num):
        if file_num == 1:
            return [col for col, var in self.field_vars1.items() if var.get() == 1]
        else:
            return [col for col, var in self.field_vars2.items() if var.get() == 1]

    def preview_merge(self):
        try:
            merged_df = self.perform_merge()
            if merged_df is not None:
                self.update_preview(self.result_preview, merged_df)
                messagebox.showinfo("成功", "预览生成成功！")
        except Exception as e:
            messagebox.showerror("错误", f"生成预览时出错：{str(e)}")

    def perform_merge(self):
        if self.df1 is None or self.df2 is None:
            messagebox.showerror("错误", "请先加载两个文件")
            return None
            
        if not self.key_var1.get() or not self.key_var2.get():
            messagebox.showerror("错误", "请选择合并的key")
            return None
            
        # 获取选中的字段
        selected_fields1 = self.get_selected_fields(1)
        selected_fields2 = self.get_selected_fields(2)
        
        if not selected_fields1 or not selected_fields2:
            messagebox.showerror("错误", "请选择需要的字段")
            return None
            
        try:
            # 准备合并的数据
            key1 = self.key_var1.get()
            key2 = self.key_var2.get()
            
            # 分别准备两个DataFrame，只包含key和选择的字段
            df1_merged = self.df1[selected_fields1]
            df2_merged = self.df2[selected_fields2]
            
            # 合并数据
            merged_df = pd.merge(
                df1_merged,
                df2_merged,
                left_on=key1,
                right_on=key2,
                how='left'
            )

            
            return merged_df
            
        except Exception as e:
            messagebox.showerror("错误",
                               f"合并数据时出错：\n{str(e)}\n\n如果两个文件中有相同的字段名，请分别选择不同的字段以避免冲突。")
            return None

    def merge_and_save(self):
        save_path = self.save_entry.get().strip()
        if not save_path:
            messagebox.showerror("错误", "请输入保存路径")
            return

        try:
            merged_df = self.perform_merge()
            if merged_df is not None:
                # 保存文件
                if save_path.endswith('.csv'):
                    merged_df.to_csv(save_path, index=False)
                else:
                    merged_df.to_excel(save_path, index=False)
                messagebox.showinfo("成功", "文件合并并保存成功！")

        except Exception as e:
            messagebox.showerror("错误", f"合并文件时出错：{str(e)}")


if __name__ == "__main__":
    root = tk.Tk()
    app = FileMergerApp(root)
    root.mainloop()
